-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: 整机power on/power off/restart/nmi/AC接口

local class = require 'mc.class'
local log = require 'mc.logging'
local m_error_custom = require 'messages.custom'
local m_enums = require 'types.enums'
local power_cycle = require 'chassis_fructrl.power_cycle'
local skynet = require 'skynet'
local utils = require 'types.utils'
local c_accycle = require 'ac_cycle'

local chassis_power_ctrl = class()

function chassis_power_ctrl:ctor(bus, object, fructrl_proxy_ins)
    self.bus = bus
    self.chassis_fructrl_obj = object
    self.fructrl_proxy_ins = fructrl_proxy_ins
    self.power_cycle_ins = power_cycle.new(object, fructrl_proxy_ins)
end

local function set_operation(obj, prop, value)
    local ok, msg = pcall(function()
        obj[prop] = value
    end)
    if not ok then
        log:error_easy('set prop %s to %s failed, error_msg:%s', prop, value, msg)
        return m_enums.RetValue.ERR
    end
    return m_enums.RetValue.OK
end

local function get_operation(obj, prop)
    local ret = nil
    local ok, msg = pcall(function()
        ret = obj[prop]
    end)
    if not ok then
        log:error_easy('get prop %s failed, error_msg:%s', prop, msg)
    end
    return ret
end

function chassis_power_ctrl:set_PowerOnStrategy(value)
    return set_operation(self.chassis_fructrl_obj, 'PowerOnStrategy', value)
end

function chassis_power_ctrl:get_PowerOnStrategy()
    return get_operation(self.chassis_fructrl_obj, 'PowerOnStrategy')
end

local function chassis_poweron_strategy(self)
    local function update_system_poweron_strategy(self)
        local value = self:get_PowerOnStrategy()
        for _, obj in pairs(self.fructrl_proxy_ins.host_objs) do
            obj.fructrl:update_chassis_poweron_strategy(value)
        end
    end
    -- 整机通电开机策略改变后,逐个设置通电开机策略
    self.chassis_fructrl_obj.property_changed:on(function(name, value)
        if name ~= 'PowerOnStrategy' then
            return
        end
        log:notice('==== chassis PowerOnStrategy to %s ====', value)
        utils.record_prop_change_operation_log(nil,
            string.format('Set chassis power restore policy to(%s) successfully', value))
        update_system_poweron_strategy(self)
    end)
    update_system_poweron_strategy(self)
    log:notice_easy("Init Cchassis PowerOnStrategy is (%s).", self.chassis_fructrl_obj.PowerOnStrategy)
end

function chassis_power_ctrl:init()
    -- 私有属性属性变更监听
    self.chassis_fructrl_obj.property_changed:on(function(name, value)
        if name ~= 'PowerGDState' then
            return
        end
        log:notice('==== chassis power good state changed to %s ====', value == 1 and 'power on' or 'power off')
        if value == 0 then
            self.chassis_fructrl_obj.PowerState = m_enums.PowerStatus.OFF
            self.chassis_fructrl_obj.PreviousPowerState = m_enums.PowerStatus.OFF
        else
            self.chassis_fructrl_obj.PowerState = m_enums.PowerStatus.ON
            self.chassis_fructrl_obj.PreviousPowerState = m_enums.PowerStatus.ON
        end
    end)
    -- 更新初始状态
    if self.chassis_fructrl_obj.PowerGDState == 0 then
        self.chassis_fructrl_obj.PowerState = m_enums.PowerStatus.OFF
    elseif self.chassis_fructrl_obj.PowerGDState == 1 then
        self.chassis_fructrl_obj.PowerState = m_enums.PowerStatus.ON
    end
    log:notice_easy("Init Chassis PowerGDStatus is (%d).", self.chassis_fructrl_obj.PowerGDState)
    chassis_poweron_strategy(self)
end

local function chassis_power_on(self, ctx, cause)
    if not self.power_cycle_ins:exit_power_cycle() then
        error(m_error_custom.OperationFailed())
    end

    self.fructrl_proxy_ins:all_host_power_on(ctx, cause)
    return 0
end

local function chassis_power_off(self, ctx, cause, type)
    if not self.power_cycle_ins:exit_power_cycle() then
        error(m_error_custom.OperationFailed())
    end

    self.fructrl_proxy_ins:all_host_power_off(ctx, cause, type)
    return 0
end

local chassis_powerctrl_api = {
    [m_enums.PowerCtrlType.On] = function (self, ctx, cause)
        return chassis_power_on(self, ctx, cause)
    end,
    [m_enums.PowerCtrlType.ForceOff] = function (self, ctx, cause)
        return chassis_power_off(self, ctx, cause, m_enums.PowerCtrlType.ForceOff)
    end,
    [m_enums.PowerCtrlType.GracefulShutdown] = function (self, ctx, cause)
        return chassis_power_off(self, ctx, cause, m_enums.PowerCtrlType.GracefulShutdown)
    end,
    [m_enums.PowerCtrlType.ForceRestart] = function (self, ctx, cause)
        self.fructrl_proxy_ins:all_host_restart(ctx, cause)
        return m_enums.RetValue.OK
    end,
    [m_enums.PowerCtrlType.Nmi] = function (self, ctx, cause)
        self.fructrl_proxy_ins:all_host_nmi(ctx, cause)
        return m_enums.RetValue.OK
    end,
    [m_enums.PowerCtrlType.PowerCycle] = function(self, ctx, cause)
        if self.power_cycle_ins:power_cycle_action(0, ctx, cause, m_enums.PowerCtrlType.PowerCycle) then
            return m_enums.RetValue.OK
        end
        return m_enums.RetValue.ERR
    end,
    [m_enums.PowerCtrlType.ForcePowerCycle] = function(self, ctx, cause)
        if self.power_cycle_ins:power_cycle_action(0, ctx, cause, m_enums.PowerCtrlType.ForcePowerCycle) then
            return m_enums.RetValue.OK
        end
        return m_enums.RetValue.ERR
    end,
    [m_enums.PowerCtrlType.ACCycle] = function(_, _, _)
        local ac_cycle = c_accycle:get_instance()
        ac_cycle:ac_down()
        return m_enums.RetValue.OK
    end,
}

local ctrl_descs = {
    [m_enums.PowerCtrlType.On] = 'power on ',
    [m_enums.PowerCtrlType.GracefulShutdown] = 'normal power off ',
    [m_enums.PowerCtrlType.ForceOff] = 'forced power off ',
    [m_enums.PowerCtrlType.PowerCycle] = 'normal power off, and then power it on ',
    [m_enums.PowerCtrlType.ForcePowerCycle] = 'forced power off, and then power it on ',
    [m_enums.PowerCtrlType.ForceRestart] = 'forced system reset ',
    [m_enums.PowerCtrlType.Nmi] = 'nmi ',
    [m_enums.PowerCtrlType.ACCycle] = 'accycle ',
}

local function has_poweronlock(fructrl_proxy)
    for _, v in pairs(fructrl_proxy.host_objs) do
        if not v.fructrl:get_PwrOnLocked() then
            return false
        end
    end
    return true
end

local function has_powerofflock(fructrl_proxy)
    for _, v in pairs(fructrl_proxy.host_objs) do
        if not v.fructrl:get_PowerOffLocked() then
            return false
        end
    end
    return true
end

function chassis_power_ctrl:power_ctrl(ctx, PowerCtrlType, cause)
    if not chassis_powerctrl_api[PowerCtrlType] then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis to ' .. ctrl_descs[PowerCtrlType] .. 'failed')
        error(m_error_custom.ValueOutOfRange('%OperateType'))
    end

    -- 整机处于下电状态时，禁止调用强制重启和强制下电再上电
    if self.chassis_fructrl_obj.PowerState == m_enums.PowerStatus.OFF and
        (PowerCtrlType == m_enums.PowerCtrlType.ForcePowerCycle or
        PowerCtrlType == m_enums.PowerCtrlType.ForceRestart or
        PowerCtrlType == m_enums.PowerCtrlType.PowerCycle) then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis to ' .. ctrl_descs[PowerCtrlType] .. 'failed')
        error(m_error_custom.OperationFailed())
    end

    -- 整机处于下电，且所有host都存在上电锁时，禁止上电
    if self.chassis_fructrl_obj.PowerState == m_enums.PowerStatus.OFF and
        PowerCtrlType == m_enums.PowerCtrlType.On and has_poweronlock(self.fructrl_proxy_ins) then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis to ' .. ctrl_descs[PowerCtrlType] .. 'failed')
        error(m_error_custom.OperationFailed())
    end

    -- 整机处于上电，且所有host都存在下电锁时，禁止下电再上电、安全下电和强制下电
    if self.chassis_fructrl_obj.PowerState == m_enums.PowerStatus.ON and
        (PowerCtrlType == m_enums.PowerCtrlType.GracefulShutdown or
        PowerCtrlType == m_enums.PowerCtrlType.ForceOff or
        PowerCtrlType == m_enums.PowerCtrlType.PowerCycle or
        PowerCtrlType == m_enums.PowerCtrlType.ForcePowerCycle) and has_powerofflock(self.fructrl_proxy_ins) then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis to ' .. ctrl_descs[PowerCtrlType] .. 'failed')
        error(m_error_custom.OperationFailed())
    end

    local ok, ret = pcall(function ()
        return chassis_powerctrl_api[PowerCtrlType](self, ctx, cause)
    end)
    if not ok then
        log:error("chassis power ctrl failed, err: %s", ret)
        return m_enums.RetValue.ERR
    end

    if ret ~= m_enums.RetValue.OK then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis to ' .. ctrl_descs[PowerCtrlType] .. 'failed')
        error(m_error_custom.OperationFailed())
    end

    log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis to ' .. ctrl_descs[PowerCtrlType] .. 'successfully')
    return ret
end

function chassis_power_ctrl:set_poweron_lock(obj, ctx, PwrOnLocked, Timeout, AppName, Reason)
    local ret = self.fructrl_proxy_ins:all_host_set_poweron_lock(PwrOnLocked, Timeout, AppName, Reason)
    if ret ~= m_enums.RetValue.OK then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis power on lock to ' .. tostring(PwrOnLocked) ..
            ' failed')
        return ret
    end

    log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis power on lock to ' .. tostring(PwrOnLocked) ..
        ' successfully')

    return ret
end

function chassis_power_ctrl:set_poweroff_lock(obj, ctx, Locked, Timeout, AppName, Reason)
    local ret = self.fructrl_proxy_ins:all_host_set_poweroff_lock(Locked, Timeout, AppName, Reason)
    if ret ~= m_enums.RetValue.OK then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis power off lock to ' .. tostring(Locked) ..
            ' failed')
        return ret
    end

    log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis power off lock to ' .. tostring(Locked) ..
        ' successfully')
    return ret
end

function chassis_power_ctrl:set_poweron_strategy_exceptions(obj, ctx, Reason, Execute, EffectivePeriod, Priority)
    local ret = self.fructrl_proxy_ins:all_host_set_poweron_strategy_exceptions(Reason, Execute, EffectivePeriod,
        Priority)
    if ret ~= m_enums.RetValue.OK then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis power on strategy exceptions to ' ..
            tostring(Execute) .. ' failed')
        return ret
    end

    log:operation(ctx:get_initiator(), 'fructrl', 'Set chassis power on strategy exceptions to ' .. tostring(Execute)..
        ' successfully')

    return ret
end

return chassis_power_ctrl